package cz.cvut.fit.mi_paa.bucket.strategy;

import cz.cvut.fit.mi_paa.abs.Context;
import cz.cvut.fit.mi_paa.abs.Strategy;
import cz.cvut.fit.mi_paa.bucket.*;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;

/**
 * Created with IntelliJ IDEA.
 * User: milos
 * Date: 20.11.12
 * Time: 1:01
 * To change this template use File | Settings | File Templates.
 */
public abstract class BucketStrategy extends Strategy {
    protected final int QUEUE_LENGTH = 20;
    protected BucketInstance mainInstance;
    protected BucketInstance sol;
    protected Set closedNodesSet = new HashSet();
    protected int openedNodesCounter;

    protected BucketStrategy(Context context) {
        super(context);
        mainInstance = (BucketInstance) context.getInstance();
    }

    @Override
    public void solve() throws IOException, CloneNotSupportedException {
        BucketContext context = (BucketContext) this.context;
        context.getSw().reset();
        context.getSw().start();
        context.setQueue(getQueue());
        mainInstance.setScoreGenerator(getScoreGenerator());


        int buckNum = mainInstance.getNumBuckets();
        enqueue(mainInstance);
        while (!context.getQueue().isEmpty()) {
            BucketInstance inst = context.getQueue().poll();
            if(inst.isSolution()) {
                context.getSw().stop();
                this.sol = inst;
                break;
            }

            for (int i = 0 ; i < buckNum; i++) {
                for (int j=i+1;j < buckNum ; j++) {
                    validateSpill(i,j,inst);
                    validateSpill(j,i,inst);
                }
            }

            for (int i = 0 ; i < buckNum; i++) {
                validateFill(i,inst);
                validateEmpty(i,inst);
            }
        }
    }

    private void validateFill(int i, BucketInstance instance) throws CloneNotSupportedException {
        if(!instance.getBuckets()[i].isFull()) {
            BucketInstance cloneInstance = instance.clone();
            cloneInstance.nextOperation(new FillOperation(i));
            cloneInstance.fill(i);
            enqueue(cloneInstance);
        }
    }

    private void validateEmpty(int i, BucketInstance instance) throws CloneNotSupportedException {
        if(!instance.getBuckets()[i].isEmpty()) {
            BucketInstance cloneInstance = instance.clone();
            cloneInstance.nextOperation(new EmptyOperation(i));
            cloneInstance.empty(i);
            enqueue(cloneInstance);
        }
    }

    private void validateSpill(int i,int j, BucketInstance instance) throws CloneNotSupportedException {
        Bucket srcBucket = instance.getBuckets()[i];
        Bucket dstBucket = instance.getBuckets()[j];
        if(srcBucket.isEmpty() || dstBucket.isFull()) return;
        BucketInstance instanceCopy = instance.clone();
        Bucket srcBucketCopy = instanceCopy.getBuckets()[i];
        Bucket dstBucketCopy = instanceCopy.getBuckets()[j];
        if(srcBucketCopy.hasEnoughWater(dstBucketCopy.getFreeSpace())) {
            dstBucketCopy.add(srcBucketCopy.pour(dstBucketCopy.getFreeSpace()));
        } else {
            dstBucketCopy.add(srcBucketCopy.pour());
        }
        instanceCopy.nextOperation(new SpillOperation(i,j));
        enqueue(instanceCopy);
    }

    private void enqueue(BucketInstance inst) {
        BucketContext context = (BucketContext) this.context;
        if(!closedNodesSet.contains(inst.hashCode())) {
            closedNodesSet.add(inst.hashCode());
            openedNodesCounter++;
            context.getQueue().offer(inst);
        }
    }

    @Override
    public void writeResult() throws IOException {
        FileWriter resultWriter = context.getResultWriter();
        resultWriter.write(mainInstance.getId() + " ");
        resultWriter.write(context.getSw().getTimeMillis() + " ");
        resultWriter.write(openedNodesCounter + " " + sol.getOperations().size());
        for (Operation operation : sol.getOperations()) {
            resultWriter.write(operation + " ");
        }
        resultWriter.write("\n");
    }

    abstract public Queue getQueue();
    abstract public BucketScoreGenerator getScoreGenerator();

    interface BucketComparator extends Comparator<BucketInstance> {};
}
